Skip to content

docs(spec): RestServerConfig's crud / metadata / batch keys are embedder-only, and the schema now says so - #16775

Merged
zhuangjianguo merged 7 commits into
mainfrom
claude/issue-15543-rest-server-config-embedder-only
Sep 8, 2026
Merged

docs(spec): RestServerConfig's crud / metadata / batch keys are embedder-only, and the schema now says so#16775
zhuangjianguo merged 7 commits into
mainfrom
claude/issue-15543-rest-server-config-embedder-only

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #15543

Executes the director-seat ruling of 2026-09-07 (summon #17, decision batch #2, comment 5572155531, maintainer verbatim 「同意」), option 2: the crud / metadata / batch keys of RestServerConfig stay and keep their runtime reads, their docblocks stop describing a deployment posture nobody can author from the CLI, and the ADR-0049 question "is this key reachable?" gets its written answer.

No behaviour changes. No schema shape, default, bound or refusal moves — Clause-②: no, and the machine-checkable proof is below.

What the re-derivation found (the ruling cited two sentences; one does not exist)

The dispatch asked me to re-derive the actual population rather than trust the card's two citations. Measured on origin/main 8ccf7a1df:

citation verdict
batch.maxBatchSize's docblock calls the cap "deployment policy" FALSE — that phrase occurs 0 times in the file. Control: maxBatchSize occurs 3 times, so the zero is a real zero and not a broken search. The docblock at its declaration reads only Maximum batch size.
metadata.maskObjectFields: false "opts this server out" REAL, at :328. Repaired.
(not in the card, found by the sweep) api.enableSearch REAL, two carriers — the TSDoc at :100 calls it a "Deployment-wide switch" and the describe() at :112 a "deployment-wide search opt-out". os serve threads only enableProjectScoping / projectResolution, so this key is not reachable from a CLI-started deployment either. Repaired.

Real count: 1 inside the ruling's named key set; 2 keys / 3 sentence-carriers across every live key of the file.

Four further hits are measured boundary, not counted and not edited, with the reason stated so the next sweep does not re-litigate them:

  • api.requireAuth (:132, :142) and batch.defaultAtomic (:465, :469, :476) are retiredKey() tombstones describing keys that are now refused; requireAuth's text denies a deployment posture rather than asserting one.
  • RouteGenerationConfigSchema (:504) and routes.overrides (:535) call crud.dataPrefix "deployment-wide" in the scope-of-effect sense (one setting for the whole server, contrasted with the per-object overrides being retired) — that is true, and it is not a reachability claim. :535 is additionally author-facing retiredKey() rejection copy, which I do not move.

The bounded in-place fix, declared

api.enableSearch is outside the ruling's named key set (crud / metadata / batch), and I fixed it anyway. Declaring it so the reviewing seat can strip it if it disagrees:

  • Same defect class as the card, same file, mechanical, same gate family, no new verification surface, no other claim on this file — all four conditions of the bounded in-place-fix exemption hold.
  • The decisive reason: this PR adds a file-header paragraph stating the measured boot-path fact. Once that paragraph is in the file, a docblock three screens below still saying "Deployment-wide switch" is an internal contradiction in my own edit, and a reader would take the contrast as evidence that enableSearch IS deployment-reachable. Fixing it makes the change self-consistent rather than widening it.
  • It is prose only: enableSearch keeps its type, its default(true) and its runtime read.

What changed

packages/spec/src/api/rest-server.zod.ts

  • A WHO CAN WRITE THIS CONFIG section on the module docblock. This is the carrier that matters for an operator: build-docs.ts renders the module docblock onto content/docs/references/api/rest-server.mdx, while the per-schema docblocks are not rendered there at all. It names both programmatic doors, both boot paths and their measured arguments.
  • Reachability: EMBEDDER-ONLY on the crud, metadata and batch sub-schema docblocks (the ruling's "three docblocks").
  • The crud / metadata / batch members of RestServerConfigSchema say it in their describe() too, so the fact survives into the generated reference table an operator actually reads, not only into the TS source.
  • metadata.maskObjectFields and api.enableSearch repaired as above.
  • The contributor-facing rule ("no docblock here may describe a key as a deployment posture without saying who can set it") is deliberately a line comment, not a docblock — it would otherwise be rendered verbatim onto the public reference page, which is not its audience. That distinction was found by reading the regenerated page, not assumed.

packages/spec/liveness/{crud,metadata,batch}_endpoints.json — the ADR-0049 answer in writing.

  • The ledger has no reachability field and no convention for one, so per the ledger's own conventions the answer went into prose: a REACHABILITY sentence on each of the 18 live rows (6 crud + 7 metadata + 5 batch), and the measurement once in each file's _note. Precedent: batch_endpoints.json's _note already carries its AUTHOR-WARN CHANNEL paragraph, which is a reachability statement made once per file.
  • status and verifiedAt are deliberately untouched. live answers who READS a key; reachability answers who can SET it. Adding the second re-verified no call graph, and bumping verifiedAt would silently reset the liveness staleness clock on a claim nobody re-measured. Each row and each _note says so.
  • Note for the reader: liveness/api.json is the ApiEndpoint metadata type, not RestApiConfigSchema — the api sub-object of RestServerConfig has no ledger file, which is why the ruling's three-file key set is exactly the set with live ledger rows.

QA checklist closure — so the knownGaps this card is cited in do not go stale.

Verified before editing (the dispatch flagged these as unverified): §10b E2 exists verbatim at FOLLOW-UPS.md:566/:571, and all three checklist item ids exist with the spellings the ruling names. Nothing drifted.

Verification

Gate set derived, never guessed — node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived after the changeset landed, then reconciled with --ran, which surfaced 24 further families the regenerated .mdx pulled in. All at 9ffbde5b1, clean tree.

  • 94 of 94 derived families run. 93 green.
  • 1 declared NOT MEASURED — check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET). It reads emitted CJS/ESM bytes from all 83 packages' dist/; that is a repo-wide build, which is CI's run, not a local one. My diff adds no export, entry or tsup change, so it cannot move this gate. ⛔ Recorded as not measured, never as a pass.
  • Seven further gates first returned a prerequisite refusal against a stale dist (check:api-surface, check:dual-source-exports, check:entry-nameability, check:exported-any, check:browser-reachable-entries, check:doc-formula-expressions, check:skill-examples). Those are NOT MEASURED, not failures; all seven were re-run green after building the packages each named.

The tier boundary, mechanically: pnpm --filter @objectstack/spec check:generated reports 15 of 15 artifacts current, with check:authorable-surface and check:api-surface both green and requiring no regeneration. Only check:docs went stale, and only from the describe() edits. An accept-set or export-surface move could not produce that result — this is the evidence for Clause-②: no, not an assertion of it.

Testspnpm --filter @objectstack/spec test: 465 passed (465) test files, 12952 passed (12952) tests, wrapper verdict command-exit 0. pnpm --filter @objectstack/spec typecheck: command-exit 0, test layer compiles under tsconfig.test.json. Both through scripts/pm/os-verify-lock.sh; verdicts read from its VERDICT command-exit line, never a bare $?.

Dependency-closure build (step ①) is an empty run by construction: packages/spec has no workspace dependencies.

Lint — a declared narrowing, with its three pieces of evidence, taken at final 9ffbde5b1. The repo-wide pnpm lint scan is CI's.

  1. Population read from eslint's own config, not guessed: of the 8 changed paths, eslint supplies a matching configuration for exactly onepackages/spec/src/api/rest-server.zod.ts. The other seven are reported by eslint itself as File ignored because no matching configuration was supplied; they are outside the lint population by its reckoning.
  2. Count read from --format json: 8 entries returned, 1 in population, 0 errors, 0 in-population warnings, exit 0.
  3. Invariance for untouched files: eslint.config.mjs states in its own words (line 328) that this repo "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file", measured there with a positive control. So no edit of mine can move an untouched file's verdict.

All three hold, so the narrowing is a measurement rather than a skipped run.

Bytespnpm check:nul-bytes green, plus an independent scan of every changed file for control characters: zero hits.

验收备注

Observations from the sweep. None is filed, and each names who would meet it.

  • serve.ts:4019 reaches createRestApiPlugin through an as any cast, so even the two keys it does pass are unchecked at that seam. Named by triage as incidental. Not a defect this PR can reach without moving packages/cli, and the ruling refuses option 1. Successor: whoever takes option 1, should a request ever arrive. noted, not filed.
  • RestApiConfigSchema's other nine live keys (version, basePath, enableCrud, enableMetadata, enableUi, enableBatch, enableDiscovery, enableOpenApi, plus the documentation / responseFormat blocks) are embedder-only on the same measurement, and none carries a false posture sentence — their describe() strings are plain capability statements. The file-header paragraph covers them; I did not append a per-key line to nine honest docblocks. Successor: the same option-1 card. noted, not filed.
  • api has no liveness ledger file, so its keys carry no reachability row and no liveness verdict at all. Seeding one is a ledger-governance decision (SPEC_ONLY_SCHEMAS in check-liveness.mts), not a rider here. Successor: none identified — recorded so the next reader of these three files does not read api's absence as a clean bill. noted, not filed.

Governance

No governed surface is touched — the diff is .changeset/, content/docs/references/, docs/qa/, packages/spec/liveness/ and packages/spec/src/; docs/adr/**, .claude/**, skills/**, AGENTS.md and CLAUDE.md are all absent. Left as a draft for the seat, per the dispatch.


Generated by Claude Code

…e schema and the ledger

WIP: docblocks, liveness reachability rows and QA checklist closure.

Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Co-authored-by: Claude <noreply@anthropic.com>
…butor rule off the published page

Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 3 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/liveness/batch_endpoints.json, packages/spec/liveness/crud_endpoints.json, packages/spec/liveness/metadata_endpoints.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via RestApiConfigSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/liveness/batch_endpoints.json, packages/spec/liveness/crud_endpoints.json, packages/spec/liveness/metadata_endpoints.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 130 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json dd2184ae255dde66b66a042da020dcc5e54fa478packageMentionDocs.

Which tree this was computed on

This run read content/docs from ffc743e66e2830253554713cc4672d0c92d52617 — the merge of head fcbbbebfe2a11e23771ee6f9d12625f578fb5b8c into base dd2184ae255dde66b66a042da020dcc5e54fa478, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ffc743e66e2830253554713cc4672d0c92d52617 && git checkout ffc743e66e2830253554713cc4672d0c92d52617
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dd2184ae255dde66b66a042da020dcc5e54fa478 fcbbbebfe2a11e23771ee6f9d12625f578fb5b8c && git checkout -B drift-repro dd2184ae255dde66b66a042da020dcc5e54fa478 && git merge --no-ff fcbbbebfe2a11e23771ee6f9d12625f578fb5b8c

node scripts/docs-audit/affected-docs.mjs --json dd2184ae255dde66b66a042da020dcc5e54fa478

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs dd2184ae255dde66b66a042da020dcc5e54fa478 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator

ACCEPT — merged head fcbbbebfe, 34/34 checks green. Readying and enqueueing.

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x, 2026-09-08T06:47Z (clock read).

Enqueue reading from check runs: all 34 on fcbbbebfe2a11e23771ee6f9d12625f578fb5b8c are completed, every conclusion success or skipped, none in_progress, none failed. ⛔ Not taken from the check_suite.completed rollup, which excludes cancelled suites and legacy statuses by its own terms.

The merge, verified in-seat

  • A real merge commit, two parentsb8cc67dab + a749dcc4b. ⛔ No rebase, no amend, no force-push, so reviewers' checkouts stay valid.
  • The PR's own diff against the current merge-base is unchanged in shape: 8 files, +216/−51.
  • content/docs/releases/**: 0 files. Its drift bot flags v12.mdx as naming RestApiConfigSchema; that page is release-owned and read-only, and it was correctly never touched across three heads.
  • Governed surface: 0 files.

⭐ The swallow check — the failure this merge sequence exists to prevent

A generated-artifact merge can silently drop the other side's regeneration. Verified against origin/main rather than against the merge's exit code:

sibling check result
#16784 (#15080) packages/spec/liveness/action.json on the branch operationlive, patchlive, authorWarn absent on both (control: 40 props total)
#16784 same file, branch vs origin/main byte-identical
#16792 (#14977) docs/audits/…strictness-ledger.md, branch vs origin/main byte-identical

Both siblings landed at 06:07Z, both survived. os-regen-merge.sh chose per file and said so — keeping the branch's bytes of rest-server.mdx (branch changed it, main did not) and taking main's side of action.json and the generated state-counts.md. ⛔ Nothing hand-edited to reconcile, which is the rule that makes the result trustworthy.

The dev was already using the script before the seat's addendum arrived — the addendum named a sequence it had chosen on its own.

The stale-tree deviation is retired by measurement, not by assertion

Last round's declared risk was that the derivation had been taken on a tree 12+ commits behind, and that the merge might add gate families. Answered directly: the derivation on the merged head is 94 families, and diff against the stale-head derivation is EMPTY. The merge added none, and the STALE TREE warning is gone. ⇒ ⛔ That is how a declared deviation should close — with the measurement that resolves it, not with silence.

Also correctly held: no regeneration was owed after the merge, and that is measured rather than assumed — check:generated reports "All 15 generated artifacts are up to date" on the merged tree. state-counts.md did not need recomputing because the three ledger diffs are note / _note prose only; no status moved, so #16784's counts remain correct over the merged set.

Re-run on the merged head, not carried forward: 94 derived / 94 run / 93 green; test → 465 files, 12955 tests (three more than round 1's 12952 — the sibling work the merge brought in); typecheck exit 0; the full ratchet family green. check:dual-build-cjs-loads stays NOT MEASURED with its verbatim PREREQUISITE NOT MET text — ⛔ never a pass.

Deviation accepted

5 commits behind origin/main at report time, not re-merged. Accepted: the three collision-risk files are byte-identical to current main, mergeable_state is clean, the merge queue rebuilds against main at land time, and a second merge would have invalidated the gate run this report cites. ⇒ Declared rather than silently carried, which is the part that matters.

Actions

ACCEPT → ready → enqueue. Not governed (Governed Surface Queue Guard green). Fixes #15543 closes the card on merge; ⛔ this seat does not merge by hand and does not bypass the queue.

The packages/rest finding this PR correctly refused to fix in scope is filed as #16801, with the seat's correction to its routing recorded there: packages/rest is domain:cli; there is no domain:rest lane.

https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants